<template>
  <el-dialog
    title="批量保存菜单"
    :visible.sync="dialogVisible"
    width="80%"
    top="13vh"
    :before-close="closeBatchMenuSaveDialogHandle"
    :close-on-click-modal="false"
    :close-on-press-escape="false"
    class="batch-menu-save"
  >

    <el-table
      :data="menuList"
      max-height="400"
      row-key="menuId"
      @row-dblclick="editMenuHandle"
    >
      <el-table-column
        type="index"
        align="center"
        label="行号"
        width="50">
      </el-table-column>
      <el-table-column header-align="center" align="center" label="名称">
        <template v-slot="{row}">
          <template v-if="row.index === editIndex">
            <el-input v-model="row.name" size="mini"></el-input>
          </template>
          <span v-else>{{ row.name }}</span>
        </template>
      </el-table-column>
      <el-table-column header-align="center" align="center" prop="name" label="父级">
        <template v-slot="{row}">
          <template v-if="row.index === editIndex">
            <el-popover
              placement="bottom-start"
              width="400"
              trigger="click"
            >
              <el-tree
                empty-text="菜单树为空"
                :data="menuTreeData"
                :props="{
                  children: 'children',
                  label: 'name'
                }"
                :expand-on-click-node="false"
                accordion
                @node-click="menuTreeClickHandle(row, $event)"
                ref="menuTree"
                style="height: 300px; overflow-y: scroll;">
              </el-tree>
              <el-input slot="reference" v-model="row.parentMenuName" :disabled="!row.name" size="mini" readonly placeholder="点击选择父级菜单" />
            </el-popover>
          </template>
          <span v-else>{{ row.parentMenuName }}</span>
        </template>
      </el-table-column>
      <el-table-column header-align="center" align="center" prop="name" label="类型" width="100">
        <template v-slot="{row}">
          <template v-if="row.index === editIndex">
            <el-select v-model="row.type" size="mini" placeholder="请选择" @change="typeChangeHandle($event, row)">
              <el-option
                v-for="item in typeOptions"
                :key="'type' + item.value"
                :label="item.label"
                :value="item.value">
              </el-option>
            </el-select>
          </template>
          <span v-else>{{ row.type === 0 ? '目录' : row.type === 1 ? '页面' : '按钮' }}</span>
        </template>
      </el-table-column>
      <el-table-column header-align="center" align="center" prop="address" label="组件路径">
        <template v-slot="{row}">
          <template v-if="row.index === editIndex">
            <el-input v-model="row.path" :disabled="row.type !== 1" size="mini" ></el-input>
          </template>
          <span v-else>{{ row.path }}</span>
        </template>
      </el-table-column>
      <el-table-column header-align="center" align="center" prop="address" label="权限标识">
        <template v-slot="{row}">
          <template v-if="row.index === editIndex">
            <el-input v-model="row.permit" :disabled="row.type !== 2" size="mini" ></el-input>
          </template>
          <span v-else>{{ row.permit }}</span>
        </template>
      </el-table-column>
      <el-table-column header-align="center" align="center" prop="name" label="图标" width="100">
        <template v-slot="{row}">
          <template v-if="row.index === editIndex">
            <el-popover
              placement="bottom-start"
              width="460"
              trigger="click"
              @show="$refs.iconSelect.reset()"
            >
              <IconSelect ref="iconSelect" @selected="iconSelectHandle($event, row)" />
              <el-input slot="reference" v-model="row.icon" :disabled="row.type === 2" size="mini" readonly placeholder="点击选择菜单图标">
                <svg-icon v-if="row.icon" slot="prefix"
                          :icon-class="row.icon"
                          style="height: 27px;width: 16px;"
                />
                <i v-else slot="prefix" class="el-icon-search el-input__icon" />
              </el-input>
            </el-popover>
          </template>
          <svg-icon v-if="row.index !== editIndex && row.icon" :icon-class="row.icon" class="fz16" />
        </template>
      </el-table-column>
      <el-table-column header-align="center" align="center" prop="address" label="排序" width="90">
        <template v-slot="{row}">
          <template v-if="row.index === editIndex">
            <el-input v-model.number="row.sort" size="mini" ></el-input>
          </template>
          <span v-else>{{ row.sort }}</span>
        </template>
      </el-table-column>
      <el-table-column header-align="center" align="center" prop="display" label="显示状态" width="100">
        <template v-slot="{row}">
          <template v-if="row.index === editIndex">
            <el-select v-model="row.display" :disabled="row.type === 2" size="mini" placeholder="请选择">
              <el-option
                v-for="item in displayOptions"
                :key="'display' + item.value"
                :label="item.label"
                :value="item.value">
              </el-option>
            </el-select>
          </template>
          <span v-else>{{ row.display === 0 ? '显示' : '隐藏' }}</span>
        </template>
      </el-table-column>
      <el-table-column fixed="right" label="操作" width="150">
        <template v-slot="{row}">
          <template v-if="editIndex !== row.index">
            <el-button type="text" @click="editMenuHandle(row, {})">编辑</el-button>
            <el-button type="text" @click="deleteMenuHandle(row)" class="danger-color">删除</el-button>
          </template>
          <template v-if="editIndex === row.index">
            <el-button type="text" @click="confirmMenuHandle(row)">确认</el-button>
            <el-button type="text" @click="cancelEditMenuHandle(row)">取消</el-button>
          </template>
        </template>
      </el-table-column>
    </el-table>

    <span slot="footer">
      <el-button @click="closeBatchMenuSaveDialogHandle">关 闭</el-button>
      <el-button :disabled="buttonLoad || menuList.length === maxRow" type="success" @click="addMenuRowHandle">增加一行</el-button>
      <el-button :disabled="buttonLoad" type="primary" @click="batchMenuSaveHandle">保 存</el-button>
    </span>
  </el-dialog>
</template>

<script>
  // 树形选择插件
  import TreeSelect from '@riophae/vue-treeselect'
  // 树形插件样式
  import '@riophae/vue-treeselect/dist/vue-treeselect.css'
  // 图标选择插件
  import IconSelect from '@/components/common/icon-select/index.vue'
  export default {
    name: "batch-menu-save",
    components: { TreeSelect, IconSelect },
    props: {
      // 弹出框是否显示
      dialogVisible: {
        type: Boolean,
        default: false
      },
    },
    data() {
      return {
        // 等待保存的菜单数据
        menuList: [{
          // 菜单主键
          menuId: '',
          // 名称
          name: '',
          // 父级菜单主键
          parentId: '',
          // 父级菜单名称
          parentMenuName: '',
          // 类型
          type: 0,
          // 图标
          icon: '',
          // 路径
          path: '',
          // 权限标识
          permit: '',
          // 排序等级
          sort: '',
          // 显示状态
          display: 0,
          // 所在数组下标
          index: 0,
          // 数据校验结果[true-校验通过 false-数据待完善]
          valid: false,
        }],
        // 最多可以同时保存菜单数据条数
        maxRow: 20,
        // 正在编译的菜单所在数组下标
        editIndex: -1,
        // 正在被编辑的菜单数据缓存（数据恢复使用）
        cacheMenu: undefined,
        // 菜单树数据
        menuTreeData: [],
        // 菜单类型枚举数组
        typeOptions: [
          {
            value: 0,
            label: '目录'
          },
          {
            value: 1,
            label: '页面'
          },
          {
            value: 2,
            label: '按钮'
          },
        ],
        // 菜单显示状态枚举数组
        displayOptions: [
          {
            value: 0,
            label: '显示'
          },
          {
            value: 1,
            label: '隐藏'
          },
        ],
        // 按钮是否可被点击[false-可以被点击 true-不可被点击]
        buttonLoad: false,
      }
    },
    methods: {
      /**
       * 初始化批量保存菜单数据弹出框方法
       */
      initBatchMenuSaveDialog() {
        // 数据加载中
        this.buttonLoad = true;

        let menuIdPromise = this.$axios.$get('/primary/get/one');
        let menuTreePromise = this.$axios.$get('/sys/menu/tree');
        Promise.all([menuIdPromise, menuTreePromise])
          .then(arr => {
            // 设置菜单主键值
            this.menuList[0].menuId = arr[0].data;
            // 添加顶层菜单
            this.menuTreeData.push(arr[1].data)
          })
          .catch(() => {})
          .finally(() => {
            // 数据加载结束
            this.buttonLoad = false;
          });
      },
      /**
       * 增加一条菜单数据事件
       */
      addMenuRowHandle() {
        let menuList = this.menuList;
        if (menuList.length === this.maxRow) {
          this.notify('', '温馨提示', `最多可以同时保存${this.maxRow}条数据`);
          return;
        }
        // 数据加载中
        this.buttonLoad = true;
        this.$axios.$get('/primary/get/one')
          .then(({ data }) => {
            // 新增一条菜单数据
            menuList.push({
              // 菜单主键
              menuId: data,
              // 名称
              name: '',
              // 父级菜单主键
              parentId: '',
              // 父级菜单名称
              parentMenuName: '',
              // 类型
              type: 0,
              // 图标
              icon: '',
              // 路径
              path: '',
              // 权限标识
              permit: '',
              // 排序等级
              sort: '',
              // 显示状态
              display: 0,
              // 所在数组下标
              index: menuList.length,
              // 数据校验结果
              valid: false
            })
            // console.log('menuList', this.menuList)
          })
          .catch(() => {})
          .finally(() => {
            // 数据加载结束
            this.buttonLoad = false;
          });
      },
      /**
       * 菜单类型下拉框选中值切换事件
       * @param type 菜单类型
       * @param menu 菜单对象
       */
      typeChangeHandle(type, menu) {
        if (type === 0) {
          // 目录菜单
          menu.path = ''
          menu.permit = '';
          menu.display = 0;
        } else if (type === 1) {
          // 页面菜单
          menu.permit = '';
          menu.display = 0;
        } else {
          // 按钮菜单
          menu.path = '';
          menu.icon = '';
          menu.display = 1;
        }
      },
      /**
       * 菜单图标选择回调事件
       * @param {string} iconName 图标名称
       * @param {Object} menu     菜单数据对象
       */
      iconSelectHandle(iconName, menu) {
        if (menu.type !== 2) {
          // 不是’按钮‘菜单，才保存图标数据
          menu.icon = iconName;
        }
      },
      /**
       * 切换至菜单编辑模式事件
       * @param {Object} menu   菜单对象
       * @param {Object} column el-table列对象
       */
      editMenuHandle(menu, column) {
        if (column.label === '操作') {
          // 双击’操作‘列，不进行编辑操作
          return;
        }
        if (this.editIndex === menu.index) {
          // 双击数据行，如果该行正在操作中，则不做操作
          return;
        }
        // console.log('editIndex', this.editIndex)

        // 切换编辑的菜单，原编辑菜单数据恢复，不做变更
        let cacheMenu = this.cacheMenu;
        if (!!cacheMenu) {
          // 缓存菜单反序列化
          cacheMenu = JSON.parse(cacheMenu);
          console.log('cacheMenu', cacheMenu)

          let preEditMenu = this.menuList[this.editIndex];
          console.log('preEditMenu', preEditMenu)

          let flag = false;
          if (!!preEditMenu.parentId && preEditMenu.parentId !== cacheMenu.parentId) {
            // 上一个编辑的菜单，如果所属父级菜单与其缓存数据不一致，则删除编辑菜单再菜单树的节点
            this.deleteMenuTreeNode(preEditMenu.menuId);
            flag = true;
          }
          if (flag && !!cacheMenu.parentId) {
            // 上一个编辑的菜单，缓存数据记录的父级菜单，重新成为其子菜单节点
            let treeNode = this.getTreeNode(cacheMenu.parentId);
            // console.log('treeNode', treeNode);
            treeNode.children.push(cacheMenu);
          }
          // 缓存数据覆盖临时数据
          this.$set(this.menuList, cacheMenu.index, cacheMenu);
        }
        // 缓存新编辑的菜单数据
        this.cacheMenu = JSON.stringify(menu);
        // console.log("cacheMenu=", this.cacheMenu);

        this.editIndex = menu.index;
      },
      /**
       * 删除待保存的菜单数据事件
       * @param {Object} menu 菜单对象
       */
      deleteMenuHandle(menu) {
        this.buttonLoad = true;
        // console.log('menu', menu)
        let children = this.getChildrenMenu(menu.menuId);

        if (!children || children.length === 0) {
          this.menuList.splice(menu.index, 1);

          if (!!menu.parentId) {
            // 只需要把树中对应的节点也删除即可
            this.deleteMenuTreeNode(menu.menuId);
          }
          this.buttonLoad = false;
        } else {
          this.$confirm('当前菜单存在子菜单，确认删除菜单及其子菜单吗？', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            // 收集将要删除的菜单主键
            let menuIds = new Set(['']);
            let queue = [ children ];
            while (queue.length > 0) {
              let arr = queue.shift();
              for (let item of arr) {
                // 记录菜单主键
                menuIds.add(item.menuId);

                if (!!item.children && item.children.length > 0) {
                  queue.push(item.children);
                }
              }
            }
            menuIds.add(menu.menuId);
            // console.log('menuIds', menuIds);

            let menuList = this.menuList, index = 0;
            for (let menuElement of menuList) {
              if (!menuIds.has(menuElement.menuId)) {
                // 移位未被删除的数据
                menuList[index++] = menuElement;
              }
            }
            // 删除数据
            menuList.splice(index, menuList.length - index);

            // 只需要把树中对应的节点也删除即可
            this.deleteMenuTreeNode(menu.menuId);
          })
          .finally(() => {
            this.buttonLoad = false;
          })
        }
      },
      /**
       * 菜单数据编辑确认事件
       * @param {Object} menu 菜单数据对象
       */
      confirmMenuHandle(menu) {
        // console.log('menu', menu)
        let count = 0;
        if (!menu.name || !menu.parentId || !menu.sort) {
          count++;
        }
        if (menu.type === 2) {
          if (!menu.permit) {
            count++;
          }
        } else {
          if (!menu.icon || !menu.path) {
            count++;
          }
        }
        // 数据填写完善，校验通过
        menu.valid = count === 0;
        if (count !== 0) {
          this.notify('', '温馨提示', '菜单数据还未完整，您可以继续编辑');
        }
        // 清理正在编辑的菜单缓存数据
        this.cacheMenu = undefined;
        // 编辑下标清空
        this.editIndex = -1;
      },
      /**
       * 取消菜单数据编辑操作
       * @param {Object} menu 菜单对象
       */
      cancelEditMenuHandle(menu) {
        let cacheMenu = JSON.parse(this.cacheMenu);
        // 从缓存数据中将菜单恢复成未修改前的数据
        this.$set(this.menuList, menu.index, cacheMenu);
        let flag = false;
        if (!!menu.parentId && menu.parentId !== cacheMenu.parentId) {
          // 取消编辑的菜单，如果所属父级菜单与其缓存数据不一致，则删除编辑菜单再菜单树的节点
          this.deleteMenuTreeNode(menu.menuId);
          flag = true;
        }
        if (flag && !!cacheMenu.parentId) {
          // 取消编辑的菜单，缓存数据记录有父级菜单，重新成为其子菜单节点
          let treeNode = this.getTreeNode(cacheMenu.parentId);
          // console.log('treeNode', treeNode);
          treeNode.children.push(cacheMenu);
        }

        // 编辑下标清空
        this.editIndex = -1;
      },
      /**
       * 菜单树节点点击事件
       * @param {Object} menu 菜单数据对象
       * @param {Object} node 菜单树节点对象
       */
      menuTreeClickHandle(menu, node) {
        // console.log('menu', menu, 'node', node)
        if (!menu.name) {
          this.notify('', '温馨提示', '请输入菜单名称后再选择父级菜单');
          return;
        }
        if (menu.menuId === node.menuId) {
          this.notify('', '温馨提示', '父级菜单不能选择自己，请确认');
          return;
        }
        // 暂存就的父级菜单主键
        let oldParentId = menu.parentId;
        // console.log('oldParentId', oldParentId);
        if (oldParentId === node.menuId) {
          // 父级菜单没有变化，结束代码执行
          document.body.click();
          return;
        }
        if (!!oldParentId) {
          let children = this.getChildrenMenu(menu.menuId);
          // console.log('children', children);
          if (!!children && this.checkParentMenu(children, node.menuId)) {
            this.notify('', '温馨提示', '父级菜单不能选择自己的子节点，请确认');
            return;
          }
          // 删除正在’编辑‘菜单在菜单树的数据，下面代码会重新添加回去
          this.deleteMenuTreeNode(menu.menuId);
        }
        menu.parentId = node.menuId;
        menu.parentMenuName = node.name;
        menu.type = node.type + 1;
        // 变更菜单类型，主动触发类型切换事件
        this.typeChangeHandle(menu.type, menu);

        if (!node.children) {
          // 原来对象没有这个属性，需要给添加上
          this.$set(node, 'children', []);
          // 设置菜单默认排序等级
          menu.sort = 1;
        } else {
          // 数组取值兼容数组元素个数为零的情况
          menu.sort = node.children[node.children.length - 1]?.sort + 1 || 1;
        }
        let menuData = {
          // 菜单主键
          menuId: menu.menuId,
          // 名称
          name: menu.name,
          // 类型
          type: menu.type,
          // 排序等级
          sort: menu.sort,
          // 子菜单数组
          children: []
        }
        // 往菜单树中添加当前正在’编辑‘的菜单数据
        node.children.push(menuData);
        // 手动调用一个事件，关闭popover弹出框
        document.body.click();
      },
      /**
       * 获取子菜单数据方法
       * 根据菜单主键，获取菜单的子菜单数据
       * @param {string} menuId 菜单主键
       * @return {Array} 子菜单数据
       */
      getChildrenMenu(menuId) {
        let queue = [ this.menuTreeData ]
        while (queue.length > 0) {
          let arr = queue.shift();

          for (let i = 0; i < arr.length; i++) {
            let item = arr[i];
            if (item.menuId === menuId) {
              // 匹配到数据
              return item.children;
            }
            if (!!item.children) {
              queue.push( item.children )
            }
          }
        }
        return [];
      },
      /**
       * 获取菜单树节点数据
       * 根据菜单主键，获取菜单对应的菜单树节点数据
       * @param {string} menuId 菜单主键
       * @returns {Object} 菜单树节点数据
       */
      getTreeNode(menuId) {
        let queue = [ this.menuTreeData ]
        while (queue.length > 0) {
          let arr = queue.shift();

          for (let i = 0; i < arr.length; i++) {
            let item = arr[i];
            if (item.menuId === menuId) {
              return item;
            }
            if (!!item.children && item.children.length > 0) {
              queue.push( item.children )
            }
          }
        }
        return undefined;
      },
      /**
       * 校验父级菜单方法
       * 根据父级菜单主键，判断点击的父级菜单是不是正在’编辑‘菜单的子节点
       * @param {Array}  menuList 正在’编辑‘菜单的直接子菜单数据
       * @param {string} parentId 点击的父级菜单主键
       * @return {boolean} true-校验拒绝 false-校验通过
       */
      checkParentMenu(menuList, parentId) {
        let queue = [ menuList ]
        while (queue.length > 0) {
          let arr = queue.shift();

          for (let menu of arr) {
            if (menu.menuId === parentId) {
              // 匹配到数据，校验拒绝
              return true;
            }
            if (!!menu.children) {
              queue.push( menu.children )
            }
          }
        }
        // 校验通过
        return false;
      },
      /**
       * 删除菜单树节点方法
       * 根据菜单主键，删除其在菜单树中的节点数据
       * @param {string} menuId 菜单主键
       */
      deleteMenuTreeNode(menuId) {
        let queue = [ this.menuTreeData ]
        while (queue.length > 0) {
          let arr = queue.shift();

          for (let i = 0; i < arr.length; i++) {
            let menu = arr[i];
            if (menu.menuId === menuId) {
              // 删除数据
              arr.splice(i, 1);
              return;
            }
            if (!!menu.children && menu.children.length > 0) {
              queue.push(menu.children)
            }
          }
        }
      },
      /**
       * 批量保存菜单数据事件
       */
      batchMenuSaveHandle() {
        let menuList = this.menuList;
        // 未校验通过的菜单下标数组
        let indexList = []
        for (let menu of menuList) {
          if (!menu.valid) {
            indexList.push(menu.index);
          }
        }
        if (indexList.length !== 0) {
          let message;
          if (indexList.length === menuList.length) {
            message = '没有一条完整的菜单数据，保存失败。';
          } else {
            message = `下标序号[${indexList}]菜单数据未填写完整，请完善。`
          }
          this.notify('warning', '温馨提示', message);
          return;
        }
        console.log('menuList', menuList)

        // 开始菜单数据保存操作
        this.buttonLoad = true;
        this.$axios.$post('/sys/menu/batch/save', menuList)
          .then(() => {
            this.notify('success', '操作成功', '批量保存菜单数据操作成功');
            // 关闭弹出框
            this.closeBatchMenuSaveDialogHandle();
          })
          .catch(() => {})
          .finally(() => {
            // 菜单数据保存操作结束
            this.buttonLoad = false;
          })
      },
      /**
       * 关闭批量保存菜单数据弹出框事件
       */
      closeBatchMenuSaveDialogHandle() {
        // 清空菜单数据
        this.menuList = [{
          // 菜单主键
          menuId: '',
          // 名称
          name: '',
          // 父级菜单主键
          parentId: '',
          // 父级菜单名称
          parentMenuName: '',
          // 类型
          type: 0,
          // 图标
          icon: '',
          // 路径
          path: '',
          // 权限标识
          permit: '',
          // 排序等级
          sort: '',
          // 显示状态
          display: 0,
          // 所在数组下标
          index: 0,
          // 数据校验结果
          valid: false
        }]
        // 清空正在编辑的下标值
        this.editIndex = -1;
        // 清空菜单缓存
        this.cacheMenu = undefined;
        // 清空菜单树数据
        this.menuTreeData = []

        // 向上传递事件
        this.$emit('close-batch-menu-save-dialog');
      }
    }
  }
</script>

<style scoped>
  .batch-menu-save >>> .el-dialog__body {
    padding: 10px 20px;
  }
  #menu-search {
    margin-bottom: 7px;
  }
</style>
